using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Gko.Utils;

namespace Gko.Video.Extensions
{
	[Serializable]
	public class AspectRatio
	{
		public static readonly AspectRatio Ratio16x5 = new AspectRatio(16, 5);
		public static readonly AspectRatio Ratio16x9 = new AspectRatio(16, 9);
		public static readonly AspectRatio Ratio17x9 = new AspectRatio(17, 9);
		public static readonly AspectRatio Ratio3x2 = new AspectRatio(3, 2);
		public static readonly AspectRatio Ratio4x3 = new AspectRatio(4, 3);
		public static readonly AspectRatio Ratio4x5 = new AspectRatio(4, 5);
		public static readonly AspectRatio Ratio5x3 = new AspectRatio(5, 3);
		public static readonly AspectRatio Ratio5x4 = new AspectRatio(5, 4);
		public static readonly AspectRatio Ratio8x3 = new AspectRatio(8, 3);
		public static readonly AspectRatio Ratio8x5 = new AspectRatio(8, 5);
		public static readonly AspectRatio Ratio16x10 = Ratio8x5;

		private static readonly AspectRatio[] standardValues = new[] { 
			Ratio5x4,
			Ratio4x3,
			Ratio4x5,
			Ratio3x2,
			Ratio8x5,
			Ratio5x3,
			Ratio16x9,
			Ratio16x5,
			Ratio17x9,
			Ratio8x3,
		};

		private readonly int height;
		private readonly bool isCustom;
		private readonly int width;

		private AspectRatio(int width, int height)
		{
			this.width = width;
			this.height = height;
			this.isCustom = false;
		}

		private AspectRatio(int width, int height, bool isCustom)
		{
			this.width = width;
			this.height = height;
			this.isCustom = isCustom;
		}

		public bool IsCustom
		{
			[DebuggerStepThrough]
			get
			{
				return this.isCustom;
			}
		}

		public int Width
		{
			[DebuggerStepThrough]
			get
			{
				return this.width;
			}
		}

		public int Height
		{
			[DebuggerStepThrough]
			get
			{
				return this.height;
			}
		}

		public AspectRatio NearestStandardRatio
		{
			get
			{
				var values = from v in standardValues
							 let item = new {
								 Diff = Math.Abs(v.Ratio - this.Ratio),
								 Value = v,
							 }
							 orderby item.Diff
							 select item.Value;
				return values.First();
			}
		}

		public bool IsStandard
		{
			get
			{
				return GetStandardRatio(this.Ratio) != null;
			}
		}

		public double Ratio
		{
			get
			{
				return GetRatio(this.width, this.height);
			}
		}

		private static double GetRatio(int width, int height)
		{
			if (height == 0) {
				return double.NaN;
			}
			return 1.0 * width / height;
		}

		private static AspectRatio InternalCreate(int width, int height, double ratio)
		{
			if (Double.IsNaN(ratio)) {
				return null;
			}
			for (var i = 2; i < height; i++) {
				var w = ratio * i;
				if (Math.Floor(w) == w) {
					return new AspectRatio((int)w, i, true);
				}
			}
			return new AspectRatio(width, height, true);
		}

		private static AspectRatio GetStandardRatio(double ratio)
		{
			return Double.IsNaN(ratio) ? null : standardValues.FirstOrDefault(standard => standard.Ratio == ratio);
		}

		public static AspectRatio Create(int width, int height)
		{
			var ratio = GetRatio(width, height);
			return GetStandardRatio(ratio) ?? InternalCreate(width, height, ratio);
		}

		public override string ToString()
		{
			return "{0}:{1} ({2:F2})".Format(this.width, this.height, this.Ratio);
		}
	}
}